package org.phalanx.logger;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;

public class SystemLogger {
	public static int QUIET = 0;
	public static int MINIMAL = 1;
	public static int NORMAL = 2;
	public static int VERBOSE = 3;
	public static int DEBUG = 4;
	
	private int logLevel;
	private boolean isFirstMessage = true;
	private FileLogger logFileWriter;
	
	public SystemLogger(int logLevel){
		this.logLevel = logLevel;
	}
	
	synchronized public void writeToConsole(String message, Object reportingClass, int loggingLevel){
		String classInstance = reportingClass.getClass().getSimpleName();
		if (logLevel >= loggingLevel){
			if (!isFirstMessage)
				System.out.println();
			System.out.println(formatMessage(message, classInstance, loggingLevel));
		}
		if (logFileWriter != null){
			logFileWriter.append(message, reportingClass, loggingLevel);
		}
	}
	
	public void initializeFileLogger(String fileName, int fileLogLevel){
		logFileWriter = new FileLogger(fileName, fileLogLevel);
	}
	
	private String formatMessage(String message, String reportingClass, int logStatus){
		String formattedString = new String();
		Timestamp t = new Timestamp(Calendar.getInstance().getTimeInMillis());
		String timeStampString = t.toString();
		timeStampString = fixMilliFormat(timeStampString);
	
		
		formattedString += timeStampString + " ";
		formattedString += "[ " + getStringFromLogLevels(logStatus) + " ] ";
		formattedString += " <" + reportingClass + ">";
		formattedString += ": " + message;
		
		return formattedString;
	}
	
	private String fixMilliFormat(String defaultString){
		String fixedMilli = new String();
		String milliSeconds = new String();
		
		milliSeconds = defaultString.substring(defaultString.length() - 3, defaultString.length());
		
		if (milliSeconds.startsWith(".")){
			fixedMilli = defaultString + "0";
		}
		else
			fixedMilli = defaultString;
		
		return fixedMilli;
	}
	
	private String getStringFromLogLevels (int logStatus){
		String logString = new String();
		
		switch(logStatus){
		case 1:
			logString = "Log";
			break;
		case 2:
			logString = "Log";
			break;
		case 3:
			logString = "Verbose Log";
			break;
		case 4:
			logString = "Debug Log";
			break;
		}
		
		return logString;
	}
	
	class FileLogger extends Thread {
		int fileLogLevel;
		String logFileName;
		File logFileObject;
		BufferedWriter out;
		boolean isBusy = false;
		ArrayList<LogLineObject> logQueue = new ArrayList<LogLineObject>();
		
		FileLogger(String fileName, int logLevel){
			fileLogLevel = logLevel;
			logFileName = fileName;
			
			try {
				if (checkIfFileExists()){
					touchFile();
				}
//				initializeFileStream();
				start();
			} catch (IOException ioe){
				ioe.printStackTrace();
			}
		}
		
		@Override
		public void run(){
			try {
			while(this != null){
				Thread.sleep(500);
				if (!logQueue.isEmpty()){
					isBusy = true;
					initializeFileStream();
					appendQueueToFile();
					out.close();
					isBusy = false;
				}
			}
			} catch (InterruptedException ie){
				
			} catch (IOException ioe){
				ioe.printStackTrace();
			}
		}
		
		private boolean checkIfFileExists(){
			boolean isExist = false;
			
			logFileObject = new File(logFileName);
			isExist = logFileObject.exists();
			
			return isExist;
		}
		
		private void touchFile() throws IOException {
			logFileObject.createNewFile();
		}
		
		private void initializeFileStream() throws IOException {
			out = new BufferedWriter(new FileWriter(logFileObject, true));
		}
		
		private void writeLine(String message, Object reportingClass, int reportingLevel){
			String classInstance = reportingClass.getClass().getSimpleName();
			StringBuffer outPutLine = new StringBuffer();
			if (fileLogLevel >= reportingLevel){
				if (!isFirstMessage)
					outPutLine.append("\n");
				outPutLine.append(formatMessage(message, classInstance, reportingLevel) + "\n");
			}
			try {
				out.append(outPutLine.toString());
			} catch (IOException ioe){
				ioe.printStackTrace();
			}
			
		}
		
		public void append(String message, Object reportingClass, int reportingLevel){
			if (!waitForBusy(2000)){
				// We would rather drop log lines here than deadlock the server
				LogLineObject newLine = new LogLineObject(message, reportingClass, reportingLevel);
				logQueue.add(newLine);
			}
		}
		
		private void appendQueueToFile(){
			for (int i = 0; i < logQueue.size(); i++){
				LogLineObject tempLine = logQueue.get(i);
				writeLine(tempLine.getMessage(), tempLine.getReportingClass(), tempLine.getReportingLevel());
			}
			logQueue.clear();
		}

		private boolean waitForBusy(int timeOut){
			boolean waitTooLong = true;
			int counter = 0;
			try {
				while(isBusy || counter == timeOut){
					Thread.sleep(10);
					counter += 10;
				}
				if (counter <= timeOut)
					waitTooLong = false;
			} catch (InterruptedException ie){

			}
			return waitTooLong;
		}
		
	}
	
	class LogLineObject {
		String message;
		Object reportingClass;
		int reportingLevel;
		
		public LogLineObject(String message, Object reportObj, int level){
			this.message = message;
			reportingClass = reportObj;
			reportingLevel = level;
		}

		public String getMessage() {
			return message;
		}

		public Object getReportingClass() {
			return reportingClass;
		}

		public int getReportingLevel() {
			return reportingLevel;
		}
	}
}
